package com.netty.my;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.netty.http.HttpServerHandler;
import com.netty.utils.MongoDbUtil;
import com.netty.common.DateUtil;
import com.netty.utils.Constants;
import com.netty.utils.Utils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.HttpConstants;
import io.netty.handler.codec.http.multipart.AbstractDiskHttpData;
import io.netty.handler.codec.http.multipart.AbstractHttpData;
import io.netty.handler.codec.http.multipart.HttpData;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import org.bson.Document;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.UUID;

public abstract class MyAbstractDiskHttpData extends AbstractHttpData {

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(AbstractDiskHttpData.class);
    private File file;
    private boolean isRenamed;
    private FileChannel fileChannel;
    private RandomAccessFile randomAccessFile;

    public void setFile(File file) {
        this.file = file;
    }

    protected MyAbstractDiskHttpData(String name, Charset charset, long size) {
        super(name, charset, size);
    }

    protected abstract String getDiskFilename();

    protected abstract String getPrefix();

    protected abstract String getBaseDirectory();

    protected abstract String getPostfix();

    protected abstract boolean deleteOnExit();

    protected abstract HttpServerHandler getHttpServerHandler();

    private File tempFile() throws IOException {

        File tmpFile = new File(getBaseDirectory() + getPrefix() +
                UUID.randomUUID().toString() + getPostfix());
        tmpFile.createNewFile();
        //插入上传任务
        File file = tmpFile;
        String uploadFileName = Utils.getUploadFileName(this);
        String name = file.getName();
        long definedLength = this.definedLength();
        HttpServerHandler httpServerHandler = getHttpServerHandler();
        //使用用户传过来的uploadId来做为id uuid
        String dataId =httpServerHandler.parameter.get("uploadId").toString();
        Document document = new Document();
        document.put("fileName", uploadFileName);
        document.put("tmpFileName", name);
        document.put("fileLength", String.valueOf(definedLength));
        String directory = httpServerHandler.parameter.get("directory").toString();
        String clientFilePath = httpServerHandler.parameter.get("clientFilePath").toString();
        directory=new String(Base64.getDecoder().decode(directory));
        clientFilePath=new String(Base64.getDecoder().decode(clientFilePath));
        document.put("directory", directory);
        document.put("clientFilePath", clientFilePath);
        document.put("createTime", DateUtil.format(new Date(), DateUtil.DATE_TIME_PATTERN));
        document.put("status", "上传中");
        document.put("_id", dataId);
        String s = document.toJson();
        ObjectMapper objectMapper = new ObjectMapper();
        getHttpServerHandler().uploadTask=objectMapper.readValue(s, HashMap.class);
        MongoDbUtil mongoDbUtil = new MongoDbUtil();
        mongoDbUtil.insertDocument(document, Constants.UPLOAD_TASK_COLLECTION);
        mongoDbUtil.close();
        return tmpFile;
    }

    @Override
    public void setContent(ByteBuf buffer) throws IOException {
        if (buffer == null) {
            throw new NullPointerException("buffer");
        } else {
            try {
                this.size = (long) buffer.readableBytes();
                this.checkSize(this.size);
                if (this.definedSize > 0L && this.definedSize < this.size) {
                    throw new IOException("Out of size: " + this.size + " > " + this.definedSize);
                }

                if (this.file == null) {
                    this.file = this.tempFile();
                }

                if (buffer.readableBytes() != 0) {
                    FileOutputStream outputStream = new FileOutputStream(this.file);

                    try {
                        FileChannel localfileChannel = outputStream.getChannel();
                        ByteBuffer byteBuffer = buffer.nioBuffer();

                        int written;
                        for (written = 0; (long) written < this.size; written += localfileChannel.write(byteBuffer)) {
                            ;
                        }

                        buffer.readerIndex(buffer.readerIndex() + written);
                        localfileChannel.force(false);
                    } finally {
                        outputStream.close();
                    }

                    this.setCompleted();
                    return;
                }

                if (!this.file.createNewFile()) {
                    throw new IOException("file exists already: " + this.file);
                }
            } finally {
                buffer.release();
            }

        }
    }

    @Override
    public void addContent(ByteBuf buffer, boolean last) throws IOException {
        if (buffer != null) {
            try {
                if (this.file == null) {
                    this.file = this.tempFile();
                }
                byte[] array = buffer.array();
                randomAccessFile = new RandomAccessFile(this.file, "rw");
                randomAccessFile.seek(this.file.length());
                randomAccessFile.write(array);
                randomAccessFile.close();
                this.size += array.length;
            } finally {
                buffer.release();
            }
        }
        if (last) {
            this.setCompleted();
        } else if (buffer == null) {
            throw new NullPointerException("buffer");
        }

    }

    @Override
    public void setContent(File file) throws IOException {
        if (this.file != null) {
            this.delete();
        }

        this.file = file;
        this.size = file.length();
        this.checkSize(this.size);
        this.isRenamed = true;
        this.setCompleted();
    }

    @Override
    public void setContent(InputStream inputStream) throws IOException {
        if (inputStream == null) {
            throw new NullPointerException("inputStream");
        } else {
            if (this.file != null) {
                this.delete();
            }

            this.file = this.tempFile();
            FileOutputStream outputStream = new FileOutputStream(this.file);
            int written = 0;

            try {
                FileChannel localfileChannel = outputStream.getChannel();
                byte[] bytes = new byte[16384];
                ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
                int read = inputStream.read(bytes);

                while (true) {
                    if (read <= 0) {
                        localfileChannel.force(false);
                        break;
                    }

                    byteBuffer.position(read).flip();
                    written += localfileChannel.write(byteBuffer);
                    this.checkSize((long) written);
                    read = inputStream.read(bytes);
                }
            } finally {
                outputStream.close();
            }

            this.size = (long) written;
            if (this.definedSize > 0L && this.definedSize < this.size) {
                if (!this.file.delete()) {
                    logger.warn("Failed to delete: {}", this.file);
                }

                this.file = null;
                throw new IOException("Out of size: " + this.size + " > " + this.definedSize);
            } else {
                this.isRenamed = true;
                this.setCompleted();
            }
        }
    }

    @Override
    public void delete() {
        if (this.fileChannel != null) {
            try {
                this.fileChannel.force(false);
                this.fileChannel.close();
            } catch (IOException var2) {
                logger.warn("Failed to close a file.", var2);
            }

            this.fileChannel = null;
        }

        if (!this.isRenamed) {
            if (this.file != null && this.file.exists() && !this.file.delete()) {
                logger.warn("Failed to delete: {}", this.file);
            }

            this.file = null;
        }

    }

    @Override
    public byte[] get() throws IOException {
        return this.file == null ? EmptyArrays.EMPTY_BYTES : readFrom(this.file);
    }

    @Override
    public ByteBuf getByteBuf() throws IOException {
        if (this.file == null) {
            return Unpooled.EMPTY_BUFFER;
        } else {
            byte[] array = readFrom(this.file);
            return Unpooled.wrappedBuffer(array);
        }
    }

    @Override
    public ByteBuf getChunk(int length) throws IOException {
        if (this.file != null && length != 0) {
            if (this.fileChannel == null) {
                FileInputStream inputStream = new FileInputStream(this.file);
                this.fileChannel = inputStream.getChannel();
            }

            int read = 0;

            ByteBuffer byteBuffer;
            int readnow;
            for (byteBuffer = ByteBuffer.allocate(length); read < length; read += readnow) {
                readnow = this.fileChannel.read(byteBuffer);
                if (readnow == -1) {
                    this.fileChannel.close();
                    this.fileChannel = null;
                    break;
                }
            }

            if (read == 0) {
                return Unpooled.EMPTY_BUFFER;
            } else {
                byteBuffer.flip();
                ByteBuf buffer = Unpooled.wrappedBuffer(byteBuffer);
                buffer.readerIndex(0);
                buffer.writerIndex(read);
                return buffer;
            }
        } else {
            return Unpooled.EMPTY_BUFFER;
        }
    }

    @Override
    public String getString() throws IOException {
        return this.getString(HttpConstants.DEFAULT_CHARSET);
    }

    @Override
    public String getString(Charset encoding) throws IOException {
        if (this.file == null) {
            return "";
        } else {
            byte[] array;
            if (encoding == null) {
                array = readFrom(this.file);
                return new String(array, HttpConstants.DEFAULT_CHARSET.name());
            } else {
                array = readFrom(this.file);
                return new String(array, encoding.name());
            }
        }
    }

    @Override
    public boolean isInMemory() {
        return false;
    }

    @Override
    public boolean renameTo(File dest) throws IOException {
        if (dest == null) {
            throw new NullPointerException("dest");
        } else if (this.file == null) {
            throw new IOException("No file defined so cannot be renamed");
        } else if (!this.file.renameTo(dest)) {
            IOException exception = null;
            FileInputStream inputStream = null;
            FileOutputStream outputStream = null;
            long chunkSize = 8196L;
            long position = 0L;

            try {
                inputStream = new FileInputStream(this.file);
                outputStream = new FileOutputStream(dest);
                FileChannel in = inputStream.getChannel();

                for (FileChannel out = outputStream.getChannel(); position < this.size; position += in.transferTo(position, chunkSize, out)) {
                    if (chunkSize < this.size - position) {
                        chunkSize = this.size - position;
                    }
                }
            } catch (IOException var23) {
                exception = var23;
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException var22) {
                        if (exception == null) {
                            exception = var22;
                        } else {
                            logger.warn("Multiple exceptions detected, the following will be suppressed {}", var22);
                        }
                    }
                }

                if (outputStream != null) {
                    try {
                        outputStream.close();
                    } catch (IOException var21) {
                        if (exception == null) {
                            exception = var21;
                        } else {
                            logger.warn("Multiple exceptions detected, the following will be suppressed {}", var21);
                        }
                    }
                }

            }

            if (exception != null) {
                throw exception;
            } else if (position == this.size) {
                if (!this.file.delete()) {
                    logger.warn("Failed to delete: {}", this.file);
                }

                this.file = dest;
                this.isRenamed = true;
                return true;
            } else {
                if (!dest.delete()) {
                    logger.warn("Failed to delete: {}", dest);
                }

                return false;
            }
        } else {
            this.file = dest;
            this.isRenamed = true;
            return true;
        }
    }

    private static byte[] readFrom(File src) throws IOException {
        long srcsize = src.length();
        if (srcsize > 2147483647L) {
            throw new IllegalArgumentException("File too big to be loaded in memory");
        } else {
            FileInputStream inputStream = new FileInputStream(src);
            byte[] array = new byte[(int) srcsize];

            try {
                FileChannel fileChannel = inputStream.getChannel();
                ByteBuffer byteBuffer = ByteBuffer.wrap(array);

                for (int read = 0; (long) read < srcsize; read += fileChannel.read(byteBuffer)) {
                    ;
                }
            } finally {
                inputStream.close();
            }

            return array;
        }
    }

    @Override
    public File getFile() throws IOException {
        return this.file;
    }

    @Override
    public HttpData touch() {
        return this;
    }

    @Override
    public HttpData touch(Object hint) {
        return this;
    }
}
